<?php

namespace common\behaviors;

use Closure;
use Yii;
use yii\helpers\ArrayHelper;
use yii\helpers\Inflector;

/**
 * AttributeTypecastBehavior provides an ability of automatic model attribute typecasting.
 *
 * ```
 * public function behaviors()
 * {
 *     return [
 *         [
 *             'class' => AttributeTypecastBehavior::class,
 *             'typecasts' => [
 *                 ActiveRecord::EVENT_BEFORE_VALIDATE => [
 *                     'timestamp' => ['begin_time', 'end_time'],
 *                     [
 *                         'attributes' => ['thumb', 'banner', 'cover'],
 *                         'type' => function ($event, $attribute) {
 *                             return Url::relativePath($this->$attribute);
 *                         },
 *                     ],
 *                 ],
 *                 ActiveRecord::EVENT_AFTER_FIND => [
 *                     [
 *                         'attributes' => ['begin_time', 'end_time'],
 *                         'type' => 'datetime',
 *                         'defaultValue' => '1986-05-30 05:11:22'
 *                     ],
 *                     [
 *                         'attributes' => ['thumb', 'banner', 'cover'],
 *                         'type' => function ($event, $attribute) {
 *                             return Url::attach($this->$attribute);
 *                         },
 *                     ],
 *                 ],
 *             ],
 *         ],
 *     ];
 * }
 * ```
 * @author emhome<emhome@163.com>
 * @since 2.0
 */
class AttributeTypecastBehavior extends \yii\behaviors\AttributesBehavior {

    const TYPE_INTEGER = 'integer';
    const TYPE_FLOAT = 'float';
    const TYPE_BOOLEAN = 'boolean';
    const TYPE_STRING = 'string';
    const TYPE_TIMESTAMP = 'timestamp';
    const TYPE_DATETIME = 'datetime';
    const TYPE_DATE = 'date';
    const TYPE_TIME = 'time';
    const TYPE_NULL = 'null';

    /**
     * @var array
     */
    public $typecasts = [];

    /**
     * @var array
     */
    private static $_typeDefaultValues = [];

    /**
     * {@inheritdoc}
     */
    public function init() {
        parent::init();

        if (!empty($this->typecasts)) {
            $this->attributes = $this->evaluateTypecasts($this->typecasts);
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function evaluateTypecasts($data) {
        $attributes = [];
        foreach ($data as $eventName => $items) {
            if (is_string($items) || empty($items)) {
                continue;
            }
            foreach ($items as $key => $vls) {
                $_defaultValue = null;
                if (is_numeric($key)) {
                    $_attributes = (array) ArrayHelper::getValue($vls, 'attributes', []);
                    $_type = ArrayHelper::getValue($vls, 'type');
                    $_defaultValue = ArrayHelper::getValue($vls, 'defaultValue');
                } else {
                    $_attributes = (array) $vls;
                    $_type = $key;
                }
                if (empty($_attributes) || !$_type) {
                    continue;
                }
                if ($_type instanceof Closure || (is_array($_type) && is_callable($_type))) {
                    foreach ($_attributes as $attributeName) {
                        if (!is_string($attributeName)) {
                            continue;
                        }
                        $attributes[$attributeName][$eventName] = $_type;
                    }
                    continue;
                }
                $method = Inflector::variablize('format_' . $_type . '_attributes');
                if (!$this->hasMethod($method)) {
                    continue;
                }
                if ($_defaultValue !== null) {
                    static::$_typeDefaultValues[$eventName][$method] = $_defaultValue;
                }
                foreach ($_attributes as $attributeName) {
                    if (!is_string($attributeName)) {
                        continue;
                    }
                    $attributes[$attributeName][$eventName] = [$this, $method];
                }
            }
        }
        return $attributes;
    }

    /**
     * {@inheritdoc}
     */
    public function formatTimestampAttributes($event, $attribute) {
        $sender = $event->sender;
        $value = $sender->$attribute;
        if ($value !== null) {
            return Yii::$app->formatter->asTimestamp($value);
        }
        return (int) $value;
    }

    /**
     * {@inheritdoc}
     */
    public function formatDatetimeAttributes($event, $attribute) {
        $sender = $event->sender;
        $value = $sender->$attribute;
        if ($value) {
            return Yii::$app->formatter->asDatetime($value);
        }
        return ArrayHelper::getValue(static::$_typeDefaultValues, [$event->name, __FUNCTION__]);
    }

}
